/*-------------------------------------------------------------------------
 *
 * signal.c
 *	  Microsoft Windows Win32 信号仿真函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/port/win32/signal.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "libpq/pqsignal.h"

/*
 * 这些是供 UNBLOCKED_SIGNAL_QUEUE() 宏使用的。
 * pg_signal_queue 必须是 volatile，因为它是由信号处理线程改变的，并且主线程在没有任何锁的情况下检查它。
 * pg_signal_mask 只有主线程改变，因此不需要它。
 */
volatile int pg_signal_queue;
int			pg_signal_mask;

HANDLE		pgwin32_signal_event;
HANDLE		pgwin32_initial_signal_pipe = INVALID_HANDLE_VALUE;

/*
 * pg_signal_crit_sec 仅用于保护 pg_signal_queue。这个变量是唯一可以被信号发送线程访问的变量！
 */
static CRITICAL_SECTION pg_signal_crit_sec;

/* 注意数组元素 0 未使用，因为它们对应于信号 0 */
static pqsigfunc pg_signal_array[PG_SIGNAL_COUNT];
static pqsigfunc pg_signal_defaults[PG_SIGNAL_COUNT];


/* 信号处理线程函数 */
static DWORD WINAPI pg_signal_thread(LPVOID param);
static BOOL WINAPI pg_console_handler(DWORD dwCtrlType);


/*
 * pg_usleep --- 延迟指定数量的微秒，但
 * 如果有信号到达，则停止等待。
 *
 * 这替代了 src/port/pgsleep.c 提供的非信号感知版本。
 */
void pg_usleep(long fc_microsec)
{
	if (unlikely(pgwin32_signal_event == NULL))
	{
		/*
		 * 如果我们在启动早期通过 pgwin32_open_handle() 到达这里
		 * 而信号事件尚未设置，请退回到常规的
		 * 不可中断睡眠。
		 */
		SleepEx((fc_microsec < 500 ? 1 : (fc_microsec + 500) / 1000), FALSE);
		return;
	}

	if (WaitForSingleObject(pgwin32_signal_event,
							(fc_microsec < 500 ? 1 : (fc_microsec + 500) / 1000))
		== WAIT_OBJECT_0)
	{
		pgwin32_dispatch_queued_signals();
		errno = EINTR;
		return;
	}
}


/* 初始化 */
void pgwin32_signal_initialize(void)
{
	int			fc_i;
	HANDLE		signal_thread_handle;

	InitializeCriticalSection(&pg_signal_crit_sec);

	for (fc_i = 0; fc_i < PG_SIGNAL_COUNT; fc_i++)
	{
		pg_signal_array[fc_i] = SIG_DFL;
		pg_signal_defaults[fc_i] = SIG_IGN;
	}
	pg_signal_mask = 0;
	pg_signal_queue = 0;

	/* 创建用于标记信号的全局事件句柄 */
	pgwin32_signal_event = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (pgwin32_signal_event == NULL)
		ereport(FATAL,
				(errmsg_internal("could not create signal event: error code %lu", GetLastError())));

	/* 创建处理信号的线程 */
	signal_thread_handle = CreateThread(NULL, 0, pg_signal_thread, NULL, 0, NULL);
	if (signal_thread_handle == NULL)
		ereport(FATAL,
				(errmsg_internal("could not create signal handler thread")));

	/* 创建控制台控制句柄以接收 Ctrl-C 等 */
	if (!SetConsoleCtrlHandler(pg_console_handler, TRUE))
		ereport(FATAL,
				(errmsg_internal("could not set console control handler")));
}

/*
 * 调度当前排队且未被阻止的所有信号
 * 被阻止的信号将被忽略，并将在 pqsigsetmask() 调用时触发。
 */
void pgwin32_dispatch_queued_signals(void)
{
	int			fc_exec_mask;

	Assert(pgwin32_signal_event != NULL);
	EnterCriticalSection(&pg_signal_crit_sec);
	while ((fc_exec_mask = UNBLOCKED_SIGNAL_QUEUE()) != 0)
	{
		/* 一个或多个未阻止的信号已排队等待执行 */
		int			fc_i;

		for (fc_i = 1; fc_i < PG_SIGNAL_COUNT; fc_i++)
		{
			if (fc_exec_mask & sigmask(fc_i))
			{
				/* 执行此信号 */
				pqsigfunc	fc_sig = pg_signal_array[fc_i];

				if (fc_sig == SIG_DFL)
					fc_sig = pg_signal_defaults[fc_i];
				pg_signal_queue &= ~sigmask(fc_i);
				if (fc_sig != SIG_ERR && fc_sig != SIG_IGN && fc_sig != SIG_DFL)
				{
					LeaveCriticalSection(&pg_signal_crit_sec);
					fc_sig(fc_i);
					EnterCriticalSection(&pg_signal_crit_sec);
					break;		/* 重启外层循环，以防信号屏蔽或
								 * 队列在信号处理程序内已被修改 */
				}
			}
		}
	}
	ResetEvent(pgwin32_signal_event);
	LeaveCriticalSection(&pg_signal_crit_sec);
}

/* 信号屏蔽。仅在主线程上调用，无需同步 */
int pqsigsetmask(int fc_mask)
{
	int			fc_prevmask;

	fc_prevmask = pg_signal_mask;
	pg_signal_mask = fc_mask;

	/*
	 * 立即调度任何排队的信号，以防我们之前已解锁
	 * 一个或多个之前排队的信号
	 */
	pgwin32_dispatch_queued_signals();

	return fc_prevmask;
}


/*
 * 类 Unix 信号处理程序安装
 *
 * 仅在主线程上调用，无需同步
 */
pqsigfunc pqsignal(int fc_signum, pqsigfunc fc_handler)
{
	pqsigfunc	fc_prevfunc;

	if (fc_signum >= PG_SIGNAL_COUNT || fc_signum < 0)
		return SIG_ERR;
	fc_prevfunc = pg_signal_array[fc_signum];
	pg_signal_array[fc_signum] = fc_handler;
	return fc_prevfunc;
}

/* 为指定的 PID 创建信号监听管道 */
HANDLE pgwin32_create_signal_listener(pid_t fc_pid)
{
	char		fc_pipename[128];
	HANDLE		pipe;
#ifdef FDD //cppcheck
	//cppcheck
	snprintf(fc_pipename, sizeof(fc_pipename), "\\\\.\\pipe\\pgsignal_%u", (unsigned int) fc_pid);
#else
	snprintf(fc_pipename, sizeof(fc_pipename), "\\\\.\\pipe\\pgsignal_%u", (int) fc_pid);
#endif
	pipe = CreateNamedPipe(fc_pipename, PIPE_ACCESS_DUPLEX,
						   PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
						   PIPE_UNLIMITED_INSTANCES, 16, 16, 1000, NULL);

	if (pipe == INVALID_HANDLE_VALUE)
		ereport(ERROR,
				(errmsg("could not create signal listener pipe for PID %d: error code %lu",
						(int) fc_pid, GetLastError())));

	return pipe;
}


/*
 * 以下所有函数在信号处理线程上执行
 * 必须对此进行同步！
 * 注意！唯一可以使用的全局变量是
 * pg_signal_queue！
 */


/*
 * 通过设置标志位和事件，为主线程排队一个信号。
 */
void pg_queue_signal(int fc_signum)
{
	Assert(pgwin32_signal_event != NULL);
	if (fc_signum >= PG_SIGNAL_COUNT || fc_signum <= 0)
		return;					/* 忽略任何错误的信号编号 */

	EnterCriticalSection(&pg_signal_crit_sec);
	pg_signal_queue |= sigmask(fc_signum);
	LeaveCriticalSection(&pg_signal_crit_sec);

	SetEvent(pgwin32_signal_event);
}

/* 信号处理线程 */
static DWORD WINAPI pg_signal_thread(LPVOID param)
{
	char		fc_pipename[128];
	HANDLE		pipe = pgwin32_initial_signal_pipe;

	/* 设置管道名称，以防我们需要重新创建管道。 */
	snprintf(fc_pipename, sizeof(fc_pipename), "\\\\.\\pipe\\pgsignal_%lu", GetCurrentProcessId());

	for (;;)
	{
		BOOL		fConnected;

		/* 如果我们没有管道，则创建一个新的管道实例。 */
		if (pipe == INVALID_HANDLE_VALUE)
		{
			pipe = CreateNamedPipe(fc_pipename, PIPE_ACCESS_DUPLEX,
								   PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
								   PIPE_UNLIMITED_INSTANCES, 16, 16, 1000, NULL);

			if (pipe == INVALID_HANDLE_VALUE)
			{
				write_stderr("could not create signal listener pipe: error code %lu; retrying\n", GetLastError());
				SleepEx(500, FALSE);
				continue;
			}
		}

		/*
		 * 等待客户端连接。如果在到达这里之前有东西连接，
		 * 我们将获得一个返回 "失败" 的结果，带有 ERROR_PIPE_CONNECTED，
		 * 这实际上是成功（好样的，微软）。
		 */
		fConnected = ConnectNamedPipe(pipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
		if (fConnected)
		{
			/*
			 * 我们有来自一个潜在信号发送者的连接。处理它。
			 */
			BYTE		sigNum;
			DWORD		bytes;

			if (ReadFile(pipe, &sigNum, 1, &bytes, NULL) &&
				bytes == 1)
			{
				/*
				 * 在响应客户端之前排队信号。通过这种方式，可以确保一旦在信号发送者中返回 kill()，信号接收者中的下一个 CHECK_FOR_INTERRUPTS() 将看到该信号。 （这比 POSIX 提供的保证更强；也许我们不需要它？但没有它，我们在 Windows 上遇到了时序错误，而在任何已知的 Unix 上都没有表现出来。）
				 */
				pg_queue_signal(sigNum);

				/*
				 * 向客户端写回一些内容，以允许其 CallNamedPipe() 调用终止。
				 */
				WriteFile(pipe, &sigNum, 1, &bytes, NULL);	/* 不在乎它
															 * 成功与否 */

				/*
				 * 我们必须等客户端读取数据后才能断开连接，否则数据将丢失。 （如果 WriteFile 调用失败，缓冲区中将没有内容，因此这不应阻塞。）
				 */
				FlushFileBuffers(pipe);
			}
			else
			{
				/*
				 * 如果我们无法从客户端读取一个字节，假设这是客户端的问题，什么都不做。也许强制关闭管道并重新打开会更好？
				 */
			}

			/* 与客户端断开连接，以便我们可以重用管道。 */
			DisconnectNamedPipe(pipe);
		}
		else
		{
			/*
			 * 连接失败。清理并重试。
			 *
			 * 这应该永远不会发生。如果发生，那么我们将错过信号，直到我们设法重新创建管道。 然而，仅仅尝试再次使用相同的管道可能行不通，因此我们别无选择。
			 */
			CloseHandle(pipe);
			pipe = INVALID_HANDLE_VALUE;
		}
	}
	return 0;
}


/* 控制台控制处理程序将在调用时由操作系统创建的线程上执行 */
static BOOL WINAPI pg_console_handler(DWORD dwCtrlType)
{
	if (dwCtrlType == CTRL_C_EVENT ||
		dwCtrlType == CTRL_BREAK_EVENT ||
		dwCtrlType == CTRL_CLOSE_EVENT ||
		dwCtrlType == CTRL_SHUTDOWN_EVENT)
	{
		pg_queue_signal(SIGINT);
		return TRUE;
	}
	return FALSE;
}
